﻿using System;
using System.Collections.Generic;
using FitNesseRunner;
using FitNesseRunner.Model;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using ITable = FitNesseRunner.Model.ITable;

namespace FitNesseRunnerTests
{
	[TestClass]
	public class PageHandlerDispatcherShould
	{
		private FakeReporter _fakeReporter;

		// TODO: Remove duplication from these tests?

		[TestInitialize]
		public void Initialize()
		{
			_fakeReporter = new FakeReporter();
		}

		[TestMethod]
		public void DispatchDirectiveToAppropriateHandler()
		{
			const string directiveName = "fakeDirective";
			var fakeHandler = new FakeDirectiveHandler(directiveName);
			
			var fakeDirective = HandleDirective(directiveName, fakeHandler);

			Assert.AreEqual(fakeDirective, fakeHandler.HandledDirective);
		}

		private static FakeDirective HandleDirective(string directiveName, IDirectiveHandler fakeHandler)
		{
			IPageObserver dispatcher = new PageHandlerDispatcher(new[] {fakeHandler}, null, null);
			
			var fakeDirective = new FakeDirective(directiveName);
			dispatcher.HandleDirective(fakeDirective);
			return fakeDirective;
		}

		[TestMethod]
		public void NotDispatchDirectiveToInappropriateHandler()
		{
			var fakeHandler = new FakeDirectiveHandler("fakeDirective");
			HandleDirective("inappropriateDirective", fakeHandler);

			Assert.IsNull(fakeHandler.HandledDirective);
		}

		[TestMethod]
		public void DispatchTableToAppropriateHandler()
		{
			const string tableName = "script";
			var fakeScriptTableHandler = new FakeTableHandler(tableName);
			var dispatcher = new PageHandlerDispatcher(new IDirectiveHandler[0], new ITableHandler[] {fakeScriptTableHandler}, null);
			var dummyTable = new DummyTable(tableName);
			dispatcher.HandleTable(dummyTable);

			Assert.AreEqual(dummyTable, fakeScriptTableHandler.HandledTable);
		}

		[TestMethod]
		public void NotDispatchTableToInappropriateHandler()
		{
			var fakeScriptTableHandler = new FakeTableHandler("script");
			var defaultHandler = new FakeTableHandler("default");
			var dispatcher = new PageHandlerDispatcher(new IDirectiveHandler[0], new ITableHandler[] { fakeScriptTableHandler }, defaultHandler);
			var dummyTable = new DummyTable("comment");
			dispatcher.HandleTable(dummyTable);

			Assert.IsNull(fakeScriptTableHandler.HandledTable);
		}

		[TestMethod]
		public void DispatchTableToDefaultHandler()
		{
			var fakeScriptTableHandler = new FakeTableHandler("script");
			var defaultHandler = new FakeTableHandler(null);
			var dispatcher = new PageHandlerDispatcher(new IDirectiveHandler[0], new ITableHandler[] { fakeScriptTableHandler }, defaultHandler);
			var dummyTable = new DummyTable("decisionTable");
			dispatcher.HandleTable(dummyTable);

			Assert.AreEqual(dummyTable, defaultHandler.HandledTable);
		}

		[TestMethod]
		public void NotDispatchTableToDefaultHandlerIfAlreadyHandled()
		{
			var fakeScriptTableHandler = new FakeTableHandler("script");
			var defaultHandler = new FakeTableHandler(null);
			var dispatcher = new PageHandlerDispatcher(new IDirectiveHandler[0], new ITableHandler[] { fakeScriptTableHandler }, defaultHandler);
			var dummyTable = new DummyTable("script");
			dispatcher.HandleTable(dummyTable);

			Assert.IsNull(defaultHandler.HandledTable);
		}

		[TestMethod]
		public void DispatchTableHandlerEvenIfWrappedWithSpaces()
		{
			const string tableName = "script";
			var fakeScriptTableHandler = new FakeTableHandler(tableName);
			var dispatcher = new PageHandlerDispatcher(new IDirectiveHandler[0], new ITableHandler[] { fakeScriptTableHandler }, null);
			var dummyTable = new DummyTable("  " + tableName + "  ");
			dispatcher.HandleTable(dummyTable);

			Assert.AreEqual(dummyTable, fakeScriptTableHandler.HandledTable);
		}

		[TestMethod]
		public void IgnoreCaseOfTableName()
		{
			const string tableName = "script";
			var fakeScriptTableHandler = new FakeTableHandler(tableName);
			var dispatcher = new PageHandlerDispatcher(new IDirectiveHandler[0], new ITableHandler[] { fakeScriptTableHandler }, null);
			var dummyTable = new DummyTable("SCript");
			dispatcher.HandleTable(dummyTable);

			Assert.AreEqual(dummyTable, fakeScriptTableHandler.HandledTable);
		}

		[TestMethod]
		public void ReportExceptionsInDirective()
		{
			const string directiveName = "fakeDirective";
			var fakeHandler = new FakeDirectiveHandler(directiveName);
			var exception = new Exception();
			fakeHandler.HandleMethod = directive => { throw exception; };
			IPageObserver dispatcher = new PageHandlerDispatcher(new IDirectiveHandler[] { fakeHandler }, null, null, _fakeReporter);
			var fakeDirective = new FakeDirective(directiveName);
			dispatcher.HandleDirective(fakeDirective);

			Assert.AreEqual(exception, _fakeReporter.Exception);
		}

		[TestMethod]
		public void ReportExceptionsInTable()
		{
			const string tableName = "script";
			var fakeScriptTableHandler = new FakeTableHandler(tableName);
			var exception = new Exception();
			fakeScriptTableHandler.HandleMethod = table => { throw exception; };
			var dispatcher = new PageHandlerDispatcher(new IDirectiveHandler[0], new ITableHandler[] { fakeScriptTableHandler }, null, _fakeReporter);
			var dummyTable = new DummyTable(tableName);
			dispatcher.HandleTable(dummyTable);

			Assert.AreEqual(exception, _fakeReporter.Exception);
		}

		private class FakeReporter : IDispatcherReporter
		{
			public Exception Exception { get; private set; }
			public void ReportException(Exception exception)
			{
				Exception = exception;
			}
		}

		public class DummyTable : ITable
		{
			private readonly string _tableName;

			public DummyTable(string tableName)
			{
				_tableName = tableName;
			}

			public IEnumerable<IEnumerable<string>> Cells
			{
				get { return new[] {new[] {_tableName}}; }
			}
		}

		public class FakeTableHandler : RegularTableHandler
		{
			public FakeTableHandler(string tableName)
				: base(tableName)
			{
				HandleMethod = table => HandledTable = table;
			}

			public ITable HandledTable { get; private set; }

			public Action<ITable> HandleMethod { get; set; }

			public override void Handle(ITable table)
			{
				HandleMethod(table);
			}
		}

		public class FakeDirective : IDirective
		{
			private readonly string _directiveName;

			public FakeDirective(string directiveName)
			{
				_directiveName = directiveName;
			}

			public string Name
			{
				get { return _directiveName; }
			}

			public string Arguments
			{
				get { throw new NotImplementedException(); }
			}
		}

		public class FakeDirectiveHandler : IDirectiveHandler
		{
			public FakeDirectiveHandler(string directiveName)
			{
				DirectiveName = directiveName;
				HandleMethod = directive => HandledDirective = directive;
			}

			public void Handle(IDirective directive)
			{
				HandleMethod(directive);
			}

			public string DirectiveName { get; private set; }

			public IDirective HandledDirective { get; private set; }

			public Action<IDirective> HandleMethod { get; set; }
		}
	}
}
